import tkinter as tk
from tkinter import ttk, filedialog, messagebox
import pandas as pd
import os
import matplotlib.pyplot as plt
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
import mplfinance as mpf
import numpy as np
import matplotlib.font_manager as fm

# --- 字体配置开始 ---
# 尝试查找系统上可用的中文字体，并优先使用
chinese_fonts_priority = ['SimHei', 'Microsoft YaHei', 'FangSong', 'STSong', 'STHeiti', 'WenQuanYi Zen Hei', 'Noto Sans CJK SC']
found_chinese_font = None
for font_name in chinese_fonts_priority:
    if fm.findfont(font_name, fallback_to_default=False):
        found_chinese_font = font_name
        break

if found_chinese_font:
    # 将找到的中文字体添加到rcParams的字体列表最前面
    plt.rcParams['font.sans-serif'] = [found_chinese_font] + plt.rcParams['font.sans-serif']
    print(f"成功找到并使用中文字体: {found_chinese_font}")
else:
    print("警告: 未找到常用中文字体。图表中的中文字符可能无法正确显示。")
    # 如果没有找到特定中文字体，则回退到原始设置，这可能仍然导致警告
    plt.rcParams['font.sans-serif'] = ['SimHei', 'Microsoft YaHei', 'DejaVu Sans']

plt.rcParams['axes.unicode_minus'] = False # 解决负号显示为方块的问题

# 创建一个 FontProperties 对象，用于在需要时明确指定字体
chinese_font_prop = None
if found_chinese_font:
    try:
        chinese_font_prop = fm.FontProperties(fname=fm.findfont(found_chinese_font))
    except Exception as e:
        print(f"创建FontProperties失败: {e}。中文字符显示可能仍有问题。")
# --- 字体配置结束 ---


class StockDataViewer:
    def __init__(self, root):
        self.root = root
        self.root.title("股票数据查看器")
        self.root.geometry("1400x900")  # 增大默认窗口尺寸
        
        # 存储处理后的数据文件列表
        self.processed_files = []
        
        # 存储当前的数据
        self.current_data = None
        
        # 用于延迟更新的定时器
        self.update_timer = None
        self.is_dragging = False
        
        # 用于控制联想功能的标志
        self.is_programmatic_change = False
        
        # 创建菜单栏
        self.create_menu()
        
        # 创建主界面
        self.create_main_interface()
        
        # 加载处理后的文件列表
        self.load_processed_files()
        
        # 默认加载数据
        self.load_default_data()
    
    def create_menu(self):
        menubar = tk.Menu(self.root)
        self.root.config(menu=menubar)
        
        # 文件菜单
        file_menu = tk.Menu(menubar, tearoff=0)
        menubar.add_cascade(label="文件", menu=file_menu)
        file_menu.add_command(label="打开CSV文件", command=self.open_file)
        file_menu.add_separator()
        file_menu.add_command(label="退出", command=self.root.quit)
        
        # 帮助菜单
        help_menu = tk.Menu(menubar, tearoff=0)
        menubar.add_cascade(label="帮助", menu=help_menu)
        help_menu.add_command(label="关于", command=self.show_about)
    
    def create_main_interface(self):
        # 创建顶部框架用于显示文件路径和选择文件
        top_frame = tk.Frame(self.root)
        top_frame.pack(fill=tk.X, padx=10, pady=5)
        
        tk.Label(top_frame, text="当前文件:").pack(side=tk.LEFT)
        self.file_path_label = tk.Label(top_frame, text="未选择文件", fg="blue")
        self.file_path_label.pack(side=tk.LEFT, padx=(5, 0))
        
        # 创建文件选择下拉框
        tk.Label(top_frame, text="选择文件:").pack(side=tk.LEFT, padx=(20, 5))
        self.file_combobox = ttk.Combobox(top_frame, width=30, state="normal")
        self.file_combobox.pack(side=tk.LEFT, padx=(0, 10))
        
        # 绑定事件
        self.file_combobox.bind("<<ComboboxSelected>>", self.on_file_selected)
        self.file_combobox.bind("<KeyRelease>", self.on_combobox_input)
        self.file_combobox.bind("<Button-1>", self.on_combobox_click)
        self.file_combobox.bind("<Return>", self.on_enter_pressed)
        
        # 刷新按钮
        refresh_btn = tk.Button(top_frame, text="刷新列表", command=self.refresh_file_list)
        refresh_btn.pack(side=tk.LEFT)

        # 添加切换到数据列表的按钮
        switch_to_data_btn = tk.Button(top_frame, text="查看数据列表", command=self.switch_to_data_tab)
        switch_to_data_btn.pack(side=tk.LEFT, padx=(10, 0))
        
        # 创建 Notebook (选项卡控件)
        self.notebook = ttk.Notebook(self.root)
        self.notebook.pack(fill=tk.BOTH, expand=True, padx=10, pady=5)
        
        # --- 创建图表选项卡 ---
        self.chart_tab = ttk.Frame(self.notebook)
        self.notebook.add(self.chart_tab, text="图表分析")
        
        # 创建日期范围控制框架 - 放在图表上方
        self.date_control_frame = tk.Frame(self.chart_tab, height=80)  # 设置固定高度
        self.date_control_frame.pack(fill=tk.X, padx=10, pady=5)
        self.date_control_frame.pack_propagate(False)  # 防止子组件改变框架大小
        
        # 创建控制面板的主容器
        control_container = tk.Frame(self.date_control_frame)
        control_container.pack(expand=True, fill=tk.BOTH)
        
        # 第一行：日期范围标签和重置按钮
        first_row = tk.Frame(control_container)
        first_row.pack(fill=tk.X, pady=(5, 0))
        
        date_label = tk.Label(first_row, text="日期范围控制:", font=("Arial", 10, "bold"))
        date_label.pack(side=tk.LEFT)
        
        # 重置按钮放在右边
        reset_btn = tk.Button(first_row, text="重置全部", command=self.reset_date_range, 
                             bg="#f0f0f0", relief="ridge")
        reset_btn.pack(side=tk.RIGHT)
        
        # 显示当前日期范围的标签
        self.date_range_label = tk.Label(first_row, text="", fg="blue", font=("Arial", 9))
        self.date_range_label.pack(side=tk.RIGHT, padx=(0, 20))
        
        # 第二行：滑动条
        second_row = tk.Frame(control_container)
        second_row.pack(fill=tk.X, pady=(5, 5))
        
        # 开始日期滑动条
        start_frame = tk.Frame(second_row)
        start_frame.pack(side=tk.LEFT, fill=tk.X, expand=True, padx=(0, 10))
        
        start_label = tk.Label(start_frame, text="开始日期:", font=("Arial", 9))
        start_label.pack(side=tk.LEFT)
        
        self.start_date_var = tk.IntVar()
        self.start_scale = tk.Scale(start_frame, 
                                   variable=self.start_date_var,
                                   orient=tk.HORIZONTAL, 
                                   from_=0, to=100,
                                   showvalue=False,  # 隐藏数值显示
                                   length=300)  # 增加长度
        self.start_scale.pack(side=tk.LEFT, fill=tk.X, expand=True, padx=(5, 0))
        
        # 绑定拖动开始和结束事件
        self.start_scale.bind("<Button-1>", self.on_drag_start)
        self.start_scale.bind("<ButtonRelease-1>", self.on_drag_end)
        self.start_scale.bind("<B1-Motion>", self.on_dragging)
        
        # 结束日期滑动条
        end_frame = tk.Frame(second_row)
        end_frame.pack(side=tk.LEFT, fill=tk.X, expand=True)
        
        end_label = tk.Label(end_frame, text="结束日期:", font=("Arial", 9))
        end_label.pack(side=tk.LEFT)
        
        self.end_date_var = tk.IntVar()
        self.end_scale = tk.Scale(end_frame, 
                                 variable=self.end_date_var,
                                 orient=tk.HORIZONTAL, 
                                 from_=0, to=100,
                                 showvalue=False,  # 隐藏数值显示
                                 length=300)  # 增加长度
        self.end_scale.pack(side=tk.LEFT, fill=tk.X, expand=True, padx=(5, 0))
        
        # 绑定拖动开始和结束事件
        self.end_scale.bind("<Button-1>", self.on_drag_start)
        self.end_scale.bind("<ButtonRelease-1>", self.on_drag_end)
        self.end_scale.bind("<B1-Motion>", self.on_dragging)
        
        # 图形框架现在是 chart_tab 的子组件，放在控制面板下方
        self.figure_frame = tk.Frame(self.chart_tab)
        self.figure_frame.pack(fill=tk.BOTH, expand=True, padx=5, pady=5)
        
        # --- 创建数据列表选项卡 ---
        self.data_tab = ttk.Frame(self.notebook)
        self.notebook.add(self.data_tab, text="数据列表")
        
        # 表格框架现在是 data_tab 的子组件
        self.data_table_frame = tk.Frame(self.data_tab)
        self.data_table_frame.pack(fill=tk.BOTH, expand=True)
        
        # 创建表格滚动条
        table_scrollbar_y = tk.Scrollbar(self.data_table_frame, orient=tk.VERTICAL)
        table_scrollbar_y.pack(side=tk.RIGHT, fill=tk.Y)
        
        table_scrollbar_x = tk.Scrollbar(self.data_table_frame, orient=tk.HORIZONTAL)
        table_scrollbar_x.pack(side=tk.BOTTOM, fill=tk.X)
        
        # 创建表格
        self.tree = ttk.Treeview(self.data_table_frame, 
                                 columns=('date', 'open', 'close', 'high', 'low', 'volume', 'amount', 'bbi', 'j', 'B1_tag'),
                                 show='headings',
                                 yscrollcommand=table_scrollbar_y.set,
                                 xscrollcommand=table_scrollbar_x.set)
        
        # 配置滚动条
        table_scrollbar_y.config(command=self.tree.yview)
        table_scrollbar_x.config(command=self.tree.xview)
        
        # 定义表头
        self.tree.heading('date', text='日期')
        self.tree.heading('open', text='开盘价')
        self.tree.heading('close', text='收盘价')
        self.tree.heading('high', text='最高价')
        self.tree.heading('low', text='最低价')
        self.tree.heading('volume', text='成交量')
        self.tree.heading('amount', text='成交额')
        self.tree.heading('bbi', text='BBI')
        self.tree.heading('j', text='J值')
        self.tree.heading('B1_tag', text='B1标记')
        
        # 设置列宽
        self.tree.column('date', width=100)
        self.tree.column('open', width=80)
        self.tree.column('close', width=80)
        self.tree.column('high', width=80)
        self.tree.column('low', width=80)
        self.tree.column('volume', width=120)
        self.tree.column('amount', width=150)
        self.tree.column('bbi', width=80)
        self.tree.column('j', width=80)
        self.tree.column('B1_tag', width=80)
        
        self.tree.pack(fill=tk.BOTH, expand=True)
        
        # 默认选中图表选项卡
        self.notebook.select(self.chart_tab)
        
        # 创建底部框架用于显示状态信息
        bottom_frame = tk.Frame(self.root)
        bottom_frame.pack(fill=tk.X, padx=10, pady=5)
        
        self.status_label = tk.Label(bottom_frame, text="就绪")
        self.status_label.pack(side=tk.LEFT)
    
    def on_combobox_input(self, event):
        """当用户输入内容时触发联想功能"""
        # 如果是程序设置的值，跳过处理
        if self.is_programmatic_change:
            return
            
        input_text = self.file_combobox.get().lower()
        
        if not input_text:
            # 如果输入为空，显示全部文件
            self.is_programmatic_change = True
            self.file_combobox['values'] = self.processed_files
            self.is_programmatic_change = False
        else:
            # 过滤匹配的文件
            filtered_files = []
            for file in self.processed_files:
                # 支持按文件名开头匹配和数字匹配
                if (input_text in file.lower() or 
                    any(input_text in part for part in file.lower().split('_'))):
                    filtered_files.append(file)
            
            # 更新下拉列表
            self.is_programmatic_change = True
            self.file_combobox['values'] = filtered_files
            self.is_programmatic_change = False
            
            # 保持输入框的焦点和光标位置
            self.file_combobox.focus_set()
    
    def on_combobox_click(self, event):
        """当点击下拉框时显示所有选项"""
        # 确保显示完整的文件列表
        self.is_programmatic_change = True
        self.file_combobox['values'] = self.processed_files
        self.is_programmatic_change = False
    
    def on_enter_pressed(self, event):
        """按回车键时尝试加载匹配的第一个文件"""
        current_values = self.file_combobox['values']
        if current_values:
            # 选择第一个匹配的文件
            first_match = current_values[0]
            self.is_programmatic_change = True
            self.file_combobox.set(first_match)
            self.is_programmatic_change = False
            
            # 加载文件
            file_path = os.path.join("TDX_data_process", first_match)
            self.load_data(file_path)
    
    def on_drag_start(self, event):
        """开始拖动时"""
        self.is_dragging = True
        # 取消之前的定时器
        if self.update_timer:
            self.root.after_cancel(self.update_timer)
            self.update_timer = None
    
    def on_dragging(self, event):
        """拖动过程中"""
        if self.is_dragging and self.current_data is not None:
            # 只更新日期范围显示，不重绘图表
            self.update_date_range_display()
    
    def on_drag_end(self, event):
        """拖动结束时"""
        self.is_dragging = False
        # 延迟500毫秒后更新图表，避免频繁重绘
        if self.update_timer:
            self.root.after_cancel(self.update_timer)
        self.update_timer = self.root.after(500, self.delayed_chart_update)
    
    def delayed_chart_update(self):
        """延迟更新图表"""
        if self.current_data is not None:
            start_idx = self.start_date_var.get()
            end_idx = self.end_date_var.get()
            
            # 确保开始日期不大于结束日期
            if start_idx > end_idx:
                start_idx, end_idx = end_idx, start_idx
                self.start_date_var.set(start_idx)
                self.end_date_var.set(end_idx)
            
            self.update_chart_with_range(start_idx, end_idx)
        self.update_timer = None
    
    def update_date_range_display(self):
        """只更新日期范围显示，不重绘图表"""
        if self.current_data is None:
            return
        
        start_idx = self.start_date_var.get()
        end_idx = self.end_date_var.get()
        
        # 确保索引有效
        total_len = len(self.current_data)
        start_idx = max(0, min(start_idx, total_len - 1))
        end_idx = max(0, min(end_idx, total_len - 1))
        
        if start_idx > end_idx:
            start_idx, end_idx = end_idx, start_idx
        
        # 获取日期范围
        start_date = self.current_data.index[start_idx]
        end_date = self.current_data.index[end_idx]
        
        # 更新日期范围显示标签
        self.date_range_label.config(
            text=f"{start_date.strftime('%Y-%m-%d')} ~ {end_date.strftime('%Y-%m-%d')} ({end_idx - start_idx + 1}天)"
        )
    
    def update_chart_with_range(self, start_idx, end_idx):
        """根据指定的索引范围更新图表"""
        if self.current_data is None:
            return
        
        # 获取日期范围
        total_len = len(self.current_data)
        start_date = self.current_data.index[start_idx]
        end_date = self.current_data.index[end_idx]
        
        # 更新日期范围显示标签
        self.date_range_label.config(
            text=f"{start_date.strftime('%Y-%m-%d')} ~ {end_date.strftime('%Y-%m-%d')} ({end_idx - start_idx + 1}天)"
        )
        
        # 筛选数据
        filtered_data = self.current_data.iloc[start_idx:end_idx+1]
        
        # 重新绘制图表
        self.plot_financial_chart(filtered_data)
    
    def reset_date_range(self):
        """重置日期范围到全部数据"""
        if self.current_data is not None:
            total_len = len(self.current_data)
            self.start_date_var.set(0)
            self.end_date_var.set(total_len - 1)
            self.update_chart_with_range(0, total_len - 1)
    
    def update_date_sliders(self, data_length):
        """更新日期滑动条的范围和默认值"""
        if data_length > 0:
            # 设置滑动条范围
            self.start_scale.config(from_=0, to=data_length-1)
            self.end_scale.config(from_=0, to=data_length-1)
            
            # 计算默认开始位置（150天前）
            default_start_idx = max(0, data_length - 150)
            default_end_idx = data_length - 1
            
            # 设置默认值（开始日期为150天前，结束日期为最新）
            self.start_date_var.set(default_start_idx)
            self.end_date_var.set(default_end_idx)
            
            # 启用滑动条
            self.start_scale.config(state='normal')
            self.end_scale.config(state='normal')
            
            # 立即更新图表和日期显示以反映默认设置
            self.update_chart_with_range(default_start_idx, default_end_idx)
        else:
            # 禁用滑动条
            self.start_scale.config(state='disabled')
            self.end_scale.config(state='disabled')
    
    def switch_to_data_tab(self):
        """切换到数据列表选项卡"""
        self.notebook.select(self.data_tab)
        self.status_label.config(text="已切换到数据列表")

    def load_processed_files(self):
        """加载处理后的文件列表"""
        self.processed_files = []
        processed_dir = "TDX_data_process"
        
        if os.path.exists(processed_dir):
            files = [f for f in os.listdir(processed_dir) if f.endswith('.csv')]
            # 按文件名排序，确保顺序一致
            self.processed_files = sorted(files)
        
        # 更新下拉框
        self.is_programmatic_change = True
        self.file_combobox['values'] = self.processed_files
        # 不要在这里设置默认值，让load_default_data()来处理
        self.is_programmatic_change = False

    def refresh_file_list(self):
        """刷新文件列表"""
        self.load_processed_files()
        self.status_label.config(text="文件列表已刷新")
    
    def on_file_selected(self, event):
        """当下拉框选择文件时触发"""
        # 如果是程序设置的值，跳过处理
        if self.is_programmatic_change:
            return
            
        selected_file = self.file_combobox.get()
        if selected_file and selected_file in self.processed_files:
            file_path = os.path.join("TDX_data_process", selected_file)
            self.load_data(file_path)
    
    def load_default_data(self):
        """加载默认数据"""
        # 首先确保文件列表已加载
        if not self.processed_files:
            self.load_processed_files()
        
        # 检查默认数据文件是否存在 (使用处理后的数据)
        default_file = os.path.join('TDX_data_process', '600036_daily.csv')
        if os.path.exists(default_file):
            self.load_data(default_file)
            # 设置下拉框选中该文件
            if '600036_daily.csv' in self.processed_files:
                self.is_programmatic_change = True
                self.file_combobox.set('600036_daily.csv')
                self.is_programmatic_change = False
        else:
            # 如果默认文件不存在，加载第一个可用的处理后文件
            if self.processed_files:
                first_file = self.processed_files[0]
                file_path = os.path.join('TDX_data_process', first_file)
                self.load_data(file_path)
                self.is_programmatic_change = True
                self.file_combobox.set(first_file)
                self.is_programmatic_change = False
            else:
                # 如果处理后的数据文件夹为空，尝试使用原始数据
                default_file = os.path.join('tdx_data', '600036_daily.csv')
                if os.path.exists(default_file):
                    self.load_data(default_file)
                    # 注意：这种情况下下拉框会显示为空，这是正常的
                    # 因为原始数据不在处理后的文件列表中
                else:
                    # 完全没有数据的情况
                    self.status_label.config(text="未找到默认数据文件，请手动选择文件")

    
    def open_file(self):
        file_path = filedialog.askopenfilename(
            title="选择CSV文件",
            filetypes=[("CSV files", "*.csv"), ("All files", "*.*")],
            initialdir="TDX_data_process"  # 默认打开处理后的数据文件夹
        )
        
        if file_path:
            self.load_data(file_path)
    
    def load_data(self, file_path):
        try:
            # 读取数据
            df = pd.read_csv(file_path)
            
            # 更新文件路径标签
            self.file_path_label.config(text=file_path)
            
            # 清空现有数据
            for item in self.tree.get_children():
                self.tree.delete(item)
            
            # 插入新数据
            for index, row in df.iterrows():
                values = (
                    row['date'],
                    f"{row['open']:.2f}" if 'open' in row else "N/A",
                    f"{row['close']:.2f}" if 'close' in row else "N/A",
                    f"{row['high']:.2f}" if 'high' in row else "N/A",
                    f"{row['low']:.2f}" if 'low' in row else "N/A",
                    f"{row['volume']:.2f}" if 'volume' in row else "N/A",
                    f"{row['amount']:.2f}" if 'amount' in row else "N/A",
                    f"{row['bbi']:.2f}" if 'bbi' in row else "N/A",
                    f"{row['j']:.2f}" if 'j' in row else "N/A",
                    f"{row['B1_tag']:.0f}" if 'B1_tag' in row else "N/A"
                )
                self.tree.insert('', tk.END, values=values)
            
            # 预处理数据用于图表绘制
            self.prepare_chart_data(df)
            
            # 更新日期滑动条（这会设置默认的150天并更新图表）
            self.update_date_sliders(len(self.current_data))
            
            # 更新状态
            self.status_label.config(text=f"已加载 {len(df)} 行数据，默认显示最近150天")
            
        except Exception as e:
            messagebox.showerror("错误", f"加载文件时出错: {str(e)}")
            self.status_label.config(text=f"加载失败: {str(e)}")
    
    def prepare_chart_data(self, df):
        """预处理图表数据"""
        # mplfinance 要求日期列作为索引，且为 datetime 类型
        if 'date' in df.columns:
            df['date'] = pd.to_datetime(df['date'])
            df = df.set_index('date')
            # 确保索引是按时间排序的
            df = df.sort_index()
            self.current_data = df
        else:
            messagebox.showwarning("数据错误", "数据中缺少'date'列，无法绘制时间序列图。")
            self.current_data = None
    
    def plot_financial_chart(self, df):
        # 清除之前的图形
        for widget in self.figure_frame.winfo_children():
            widget.destroy()
        
        if df is None or df.empty:
            return

        # 从文件路径获取股票代码和名称
        file_name = os.path.basename(self.file_path_label.cget("text"))
        # 解析文件名，支持两种格式：
        # 1. {code}_daily.csv
        # 2. {code}_{name}_daily.csv
        parts = file_name.replace('_daily.csv', '').split('_')
        stock_code = parts[0]
        stock_name = parts[1] if len(parts) > 1 else "未知"

        # 检查必要的列是否存在
        required_cols = ['open', 'high', 'low', 'close', 'volume']
        if not all(col in df.columns for col in required_cols):
            messagebox.showwarning("数据错误", f"绘图所需列不完整。需要: {', '.join(required_cols)}")
            return
        
        # 创建 addplot 列表，用于添加额外的线或散点图
        apds = []

        # 1. 添加 BBI 线到主图 (panel=0)
        if 'bbi' in df.columns:
            apds.append(mpf.make_addplot(df['bbi'], color='red', linestyle='--', panel=0, width=0.7, title='BBI'))

        # 2. 添加 J 值到单独的子图 (panel=1)
        if 'j' in df.columns:
            # J值通常在0-100之间波动，可以设置ylim
            apds.append(mpf.make_addplot(df['j'], color='purple', panel=1, ylabel='J-Value', ylim=(0,100)))
            # 绘制J值的超买超卖线
            apds.append(mpf.make_addplot(pd.Series(80, index=df.index), color='gray', linestyle=':', panel=1, width=0.5))
            apds.append(mpf.make_addplot(pd.Series(20, index=df.index), color='gray', linestyle=':', panel=1, width=0.5))

        # 3. 添加 B1_tag 标记到主图 (panel=0)
        if 'B1_tag' in df.columns:
            # 标记B1_tag为1的点 (向上箭头)
            b1_positive_dates = df[df['B1_tag'] == 1].index
            b1_positive_close = df[df['B1_tag'] == 1]['close']
            if not b1_positive_dates.empty:
                # 使用NaN填充非标记日期，这样mplfinance只会绘制有值的点
                scatter_data_pos = pd.Series(np.nan, index=df.index)
                scatter_data_pos.loc[b1_positive_dates] = b1_positive_close
                apds.append(mpf.make_addplot(scatter_data_pos, type='scatter', marker='^', markersize=100, color='green', panel=0, label='B1_tag = 1'))
            
            # 标记B1_tag为-1的点 (向下箭头)
            b1_negative_dates = df[df['B1_tag'] == -1].index
            b1_negative_close = df[df['B1_tag'] == -1]['close']
            if not b1_negative_dates.empty:
                scatter_data_neg = pd.Series(np.nan, index=df.index)
                scatter_data_neg.loc[b1_negative_dates] = b1_negative_close
                apds.append(mpf.make_addplot(scatter_data_neg, type='scatter', marker='v', markersize=100, color='orange', panel=0, label='B1_tag = -1'))

        # 定义面板布局：(主图高度比例, J值图高度比例, ...)
        # mplfinance会自动添加成交量图，其高度比例默认是1
        panel_ratios = [3]
        if 'j' in df.columns:
            panel_ratios.append(1) # 如果有J值，则增加一个面板比例

        # 绘制金融图表
        # style='yahoo' 或 'charles' 等样式通常比较清晰
        # volume=True 会自动在底部添加成交量图
        fig, axes = mpf.plot(
            df, 
            type='candle', # K线图
            style='yahoo', # 图表风格
            volume=True,   # 显示成交量
            addplot=apds,  # 添加自定义的线和散点
            panel_ratios=panel_ratios, # 设置面板高度比例
            figscale=1.2,  # 稍微减小图表比例以适应控制面板
            returnfig=True, # 返回 Matplotlib 的 Figure 对象
            warn_too_much_data=len(df) + 1 # 抑制"数据过多"警告
        )
        
        # mplfinance 默认的标题位置可能不好看，可以手动设置
        # fig.suptitle 用于设置整个 Figure 的主标题
        if fig:
            if chinese_font_prop:
                fig.suptitle(f'{stock_code} {stock_name} - 股票价格走势与指标', fontsize=14, fontproperties=chinese_font_prop)
            else:
                fig.suptitle(f'{stock_code} {stock_name} - 股票价格走势与指标', fontsize=14)

        # axes 列表包含每个子图的 Axes 对象。
        # axes[0] 是主图（K线图），axes[-1] 是成交量图。
        # 如果有J值图，它通常是 axes[1]。
        if axes and axes[0]:
            # 设置主图的Y轴标签
            if chinese_font_prop:
                axes[0].set_ylabel('价格', fontsize=10, fontproperties=chinese_font_prop)
            else:
                axes[0].set_ylabel('价格', fontsize=10)
            
            # 设置成交量图的Y轴标签 (axes[-1] 总是成交量图)
            if len(axes) > 1 and 'volume' in df.columns:
                if chinese_font_prop:
                    axes[-1].set_ylabel('成交量', fontsize=10, fontproperties=chinese_font_prop)
                else:
                    axes[-1].set_ylabel('成交量', fontsize=10)

            # 设置J值图的Y轴标签（如果存在）
            # J值图是第二个面板，即 axes[1]
            if 'j' in df.columns and len(axes) > 2: # 确保有J值面板和成交量面板
                if chinese_font_prop:
                    axes[1].set_ylabel('J值', fontsize=10, fontproperties=chinese_font_prop)
                else:
                    axes[1].set_ylabel('J值', fontsize=10)

        # 将图形嵌入到Tkinter界面中
        canvas = FigureCanvasTkAgg(fig, master=self.figure_frame)
        canvas.draw()
        canvas.get_tk_widget().pack(fill=tk.BOTH, expand=True)
        
        # 重要的：关闭Matplotlib的Figure，防止内存泄漏
        plt.close(fig)
    
    def show_about(self):
        messagebox.showinfo("关于", "股票数据查看器\n版本 1.0\n基于mplfinance库，用于查看股票历史数据和指标")

def main():
    root = tk.Tk()
    app = StockDataViewer(root)
    root.mainloop()

if __name__ == "__main__":
    main()
